#pragma once

#include "option.h"

namespace sfc::iter {

using option::Option;

template <class Self, class Item>
struct Iterator {
  auto advance_by(usize n) -> usize {
    auto& self = static_cast<Self&>(*this);
    for (usize i = 0; i < n; ++i) {
      if (!self.next()) {
        return i;
      }
    }
    return n;
  }

  auto nth(usize n) -> Option<Item> {
    auto& self = static_cast<Self&>(*this);
    this->advance_by(n);
    return self.next();
  }

  template <class B>
  auto fold(B init, auto&& f) -> B {
    auto& self = static_cast<Self&>(*this);

    auto accum = init;
    while (auto x = this->next()) {
      accum = f(accum, ~x);
    }
    return accum;
  }

  void for_each(auto&& f) {
    auto& self = static_cast<Self&>(*this);
    while (auto x = self.next()) {
      f(~x);
    }
  }

  auto find(auto&& p) -> Option<Item> {
    auto& self = static_cast<Self&>(*this);

    while (auto x = self.next()) {
      if (p(~x)) {
        return x;
      }
    }
    return {};
  }

  auto rfind(auto&& p) -> Option<Item> {
    auto& self = static_cast<Self&>(*this);

    while (auto x = self.next_back()) {
      if (p(~x)) {
        return x;
      }
    }
    return {};
  }

  auto position(auto&& p) -> Option<usize> {
    auto& self = static_cast<Self&>(*this);
    for (auto i = usize(0); auto x = self.next(); ++i) {
      if (p(~x)) {
        return i;
      }
    }
    return {};
  }

  auto rposition(auto&& p) -> Option<usize> {
    auto& self = static_cast<Self&>(*this);
    for (auto i = self.len() - 1; auto x = self.next_back(); --i) {
      if (p(~x)) {
        return i;
      }
    }
    return {};
  }
};

}  // namespace sfc::iter
